/*
把其他数据类型转换为Number
  Number([val])
    + 一般用于浏览器的“隐式转换”中
        @1 数学运算
        @2 isNaN检测
        @3 ==比较
        ...
    + 规则：
        @1 字符串转换为数字：空字符串变为0，如果出现任何非有效数字字符，结果都是NaN
        @2 把布尔转换为数字：true->1  false->0
        @3 null->0  undefined->NaN
        @4 Symbol无法转换为数字，会报错：Uncaught TypeError: Cannot convert a Symbol value to a number
        @5 BigInt去除“n”（超过安全数字的，会按照科学计数法处理）
        @6 把对象转换为数字：
          + 先调用对象的 Symbol.toPrimitive 这个方法
            如果不存在这个方法：则继续下一步处理
            如果存在这个方法：则把这个方法执行，传递"number"标识进去
          + 再调用对象的 valueOf 获取原始值
            如果获取的值是原始值：这就是转换的结果
            如果不是原始值，则继续下一步
          + 再调用对象的 toString 把其变为字符串
          + 最后再把字符串基于Number方法转换为数字
  parseInt([val])  parseFloat([val])
    + 一般用于手动转换
    + 规则：[val]值必须是一个字符串，如果不是则先转换为字符串；然后从字符串左侧第一个字符开始找，把找到的有效数字字符最后转换为数字「一个都没找到就是NaN」；遇到一个非有效数字字符，不论后面是否还有有效数字字符，都不再查找了；parseFloat可以多识别一个小数点；
  parseInt([val],[radix])
    从[val]左侧开始进行查找，找到所有符合[radix]进制的内容，然后把其按照[radix]进制转换为10进制！！
    + [radix]是设置的进制，取值有效范围是2~36之间，如果不在有效范围内，结果就是NaN
    + [radix]不写或者设置的为0，默认就是10「特殊情况：如果[val]是以“0x”开始的，则默认值是16」


把其他数据类型转换为字符串
  也分为：隐式转换和显示转换
  方法有：String([val]) 或者 [val].toString() 等
  隐式转换使用的都是String([val])这种方式
  ---
  String([val]) 和 [val].toString() 的处理机制是不一样的
  [val].toString()规则：
    @1 基于原型链查找机制，找私有和公有上的toString方法
    @2 把找到的方法执行即可！！-> 如果用的是Object.prototype.toString方法，不是用来转字符串，而是检测数据类型的！！
  String([val])规则：
    @1 对于其它原始值来讲，基本上就是拿字符串包起来
    @2 对象转字符串
      + String(对象)：按照 先找Symbol.toPrimitive -> 再调用valueOf -> 最后调用toString来处理
  “+”除数学运算，还可能代表的字符串拼接
    + 有两边，一边是字符串，肯定是字符串拼接
    + 有两边，一边是对象，则可能是字符串拼接，还有可能是数学运算
    + 只出现在左边，例如：+"10" 这种方式就是把其它值转换为数字
    + 遇到 ++i 或者 i++ ，一定是把i的值变为数字进行累加的
    + ...


把其他数据类型转换为Boolean
  转换规则：除了“0/NaN/空字符串/null/undefined”五个值是false，其余都是true
  出现情况：
    @1 Boolean([val]) 或者 !/!!
    @2 条件判断
    ...


“==”相等，两边数据类型不同，需要先转为相同类型，然后再进行比较
    @1 对象==字符串  对象转字符串
    @2 null==undefined  -> true   null/undefined和其他任何值都不相等
       null===undefined -> false
    @3 对象==对象  比较的是堆内存地址，地址相同则相等
    @4 NaN!==NaN  NaN和任何值(包含本身)都不相等
    @5 除了以上情况，只要两边类型不一致，剩下的都是转换为数字，然后再进行比较的
“===”绝对相等，如果两边类型不同，则直接是false，不会转换数据类型「推荐」


Object.is([val1],[val2]) 检测两个值是否相等「ES6新增的」
  + 核心用的是“===”
  + 特殊：Object.is(NaN,NaN) => true
*/

/*
 方案一：当前的比较是基于“==”来处理的，所以a的值只要不是数字类型，都会隐式转换为数字类型「Number(a)」
   @1 经过初步分析，a只能是一个对象 
   @2 把对象基于Number进行隐式转换，会经历 Symbol.toPrimitive -> valueOf -> toString -> Number
   @3 所以我们只要对其转换的某一步进行重写，让每一次转换分别返回 1/2/3 ，就可以实现需求了
 */
/* 
var a = { x: 0 };
a[Symbol.toPrimitive] = function toPrimitive() {
  return ++a.x;
};
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
} 
*/

/* 
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
} 
*/

/* 
 方案二：在全局上下文中，基于var声明的变量，会放在GO中（相当于给window设置一个属性）
   @1 以后 a==1 ，就是获取window.a的值，和1进行比较
   @2 既然这样，我们就可以对window.a做数据劫持，在获取其值的时候，让每一次返回 1/2/3「GET函数」
 */
var i = 0;
Object.defineProperty(window, 'a', {
  get() {
    return ++i;
  }
});
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
}


//===============================
/* let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
// 100 + true + 21.2 + null -> 122.2
// 122.2 + undefined -> NaN
// NaN + "Tencent" -> "NaNTencent"
// 往后所有操作都是字符串拼接 "NaNTencentnull9false" */

//===============================
// let obj = {};
// let arr = [10];
// let time = new Date();
// let num = new Number(10);

/* console.log(Number(obj)); //NaN
// obj[Symbol.toPrimitive] -> undefined
// obj.valueOf() -> {}
// obj.toString() -> '[object Object]'
// Number('[object Object]') -> NaN */

/* console.log(Number(arr)); //10
// arr[Symbol.toPrimitive] -> undefined
// arr.valueOf() -> [10]
// arr.toString() -> '10'
// Number('10') -> 10 */

/* console.log(Number(time)); //时间戳（距离1970.1.1毫秒差）
// time[Symbol.toPrimitive] -> 函数
// time[Symbol.toPrimitive]('number')  调用此方法会传递‘number’、‘string’、‘default’ */

/* console.log(Number(num)); //10
// num[Symbol.toPrimitive] -> undefined
// num.valueOf() -> 10 */